﻿// -----------------------------------------------------------------------
// <copyright file="Character.cs" company="Diddly Squad">
// </copyright>
// -----------------------------------------------------------------------

using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using FarseerPhysics.Factories;

namespace DiddlySquat
{
	/// <summary>
	/// Character base class. All game characters derive from this class.
	/// </summary>
	public abstract class Character
	{
		#region Properties

		private World world;
		
		public Body Body { get; set; }
		public Texture2D Sprite { get; set; }

		protected Texture2D CharacterDeathSprite;
        protected Texture2D CharacterExitSprite;
		private Vector2 animationPosition;

		public float SizeFactor { get; protected set; }

		public int JumpsLeft;
		public int MaxJumps;
		public float JumpStrength;

		public Vector2 Position
		{
			get
			{
				return this.Body.Position;
			}
		}

		// Animation representing the player
		public Animation DeathAnimation { get; protected set; }
        public Animation ExitAnimation { get; protected set; }

		// Position of the Player relative to the upper left side of the screen
		public Vector2 Origin { get; protected set; }

		// State of the player
		public bool Active { get; protected set; }
		
		// Amount of hit points that player has
		public double Health { get; protected set; }

		public Weapon Weapon { get; set; }

        public bool IsExiting { get; set; }

		//Way to attach weapon to player
		public FarseerPhysics.Dynamics.Joints.SliderJoint Joint { get; private set; }

		#endregion

		#region Public Methods

		/// <summary>
		/// Create character to the world.
		/// </summary>
		/// <param name="world">The world.</param>
		/// <param name="position">The position.</param>
		/// <param name="body">The body.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="health">The health.</param>
		/// <param name="weapon">The weapon.</param>
		public virtual void Initialize(World world, Vector2 position, Body body, Texture2D sprite, double health, Weapon weapon)
		{
			this.Initialize(world, position, body, sprite, health);
			this.Weapon = weapon;
			this.Joint = JointFactory.CreateSliderJoint(world, this.Weapon.Body, this.Body, Vector2.Zero, Vector2.Zero, 0.1f, 0.1f);
		}

		/// <summary>
		/// Create character to the world.
		/// </summary>
		/// <param name="world">The world.</param>
		/// <param name="position">The position.</param>
		/// <param name="body">The body.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="health">The health.</param>
		public virtual void Initialize(World world, Vector2 position, Body body, Texture2D sprite, double health)
		{
			this.Sprite = sprite;
			this.Origin = new Vector2(this.Sprite.Width / 2f, this.Sprite.Height / 2f);
			this.Body = body;
			this.world = world;
			this.Body.OnCollision += HandleCollision;

			// Set the character to be active
			this.Active = true;

			// Set the character health
			this.Health = health;

			this.DeathAnimation = new Animation();
            this.ExitAnimation = new Animation();
		}

		/// <summary>
		/// Create character to the world.
		/// </summary>
		/// <param name="world">The world.</param>
		/// <param name="position">The position.</param>
		/// <param name="body">The body.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="health">The health.</param>
		/// <param name="deathSprite">The death sprite.</param>
		public virtual void Initialize(World world, Vector2 position, Body body, Texture2D sprite, double health, Texture2D deathSprite)
		{
			this.CharacterDeathSprite = deathSprite;
			this.Initialize(world, position, body, sprite, health);
		}

		/// <summary>
		/// Create character to the world.
		/// </summary>
		/// <param name="world">The world.</param>
		/// <param name="position">The position.</param>
		/// <param name="body">The body.</param>
		/// <param name="sprite">The sprite.</param>
		/// <param name="health">The health.</param>
		/// <param name="weapon">The weapon.</param>
		/// <param name="deathSprite">The death sprite.</param>
		public virtual void Initialize(World world, Vector2 position, Body body, Texture2D sprite, double health, Weapon weapon, Texture2D deathSprite)
		{
			this.CharacterDeathSprite = deathSprite;
			this.Initialize(world, position, body, sprite, health, weapon);
		}

        /// <summary>
        /// Create character to the world.
        /// </summary>
        /// <param name="world">The world.</param>
        /// <param name="position">The position.</param>
        /// <param name="body">The body.</param>
        /// <param name="sprite">The sprite.</param>
        /// <param name="health">The health.</param>
        /// <param name="weapon">The weapon.</param>
        /// <param name="deathSprite">The death sprite.</param>
        /// <param name="exitSprite">The sprite containing the animation that is played when player gets to the end of a level.</param>
        public virtual void Initialize(World world, Vector2 position, Body body, Texture2D sprite, double health, Weapon weapon, Texture2D deathSprite, Texture2D exitSprite)
        {
            this.CharacterExitSprite = exitSprite;
            this.Initialize(world, position, body, sprite, health, weapon, deathSprite);
        }

		/// <summary>
		/// Updates the character animation.
		/// </summary>
		/// <param name="gameTime">The game time.</param>
		public virtual void Update(GameTime gameTime)
		{
            if (this.IsExiting)
            {
                ExitAnimation.Update(gameTime);
            }
			else if (this.IsDead())
			{
				DeathAnimation.Update(gameTime);
			}
		}

		/// <summary>
		/// Draws the character.
		/// </summary>
		/// <param name="spriteBatch">The sprite batch.</param>
		public virtual void Draw(SpriteBatch spriteBatch)
		{
            if (this.IsExiting) {
                this.ExitAnimation.Draw(spriteBatch);
            }
            else if (this.IsDead())
            {
                DeathAnimation.Draw(spriteBatch);
            }
            else
            {
                spriteBatch.Draw(
                        this.Sprite, this.Body.Position * Constants.MeterInPixels, null, Color.White,
                        this.Body.Rotation, this.Origin, 1f, SpriteEffects.None, 0f);
            }
				
		}

		/// <summary>
		/// Hits the character with specified damage.
		/// </summary>
		/// <param name="damage">The damage.</param>
		public void Hit(double damage)
		{
			this.Health -= damage;
			if (Health <= 0)
				this.Kill();
		}

		/// <summary>
		/// Determines whether this character is dead.
		/// </summary>
		/// <returns>
		///   <c>true</c> if this character is dead; otherwise, <c>false</c>.
		/// </returns>
		public bool IsDead()
		{
			if (this.Health <= 0)
			{
				return true;
			}
			return false;
		}

        public bool IsWeaponAvailable()
        {
            if (this.Weapon != null)
            {
                if (this.Weapon.Active)
                    return true;
            }
            return false;
        }

		#endregion

		/// <summary>
		/// Kills the character and runs animation if not specified otherwise.
		/// </summary>
		/// <param name="runAnimation">if set to <c>true</c> [run animation].</param>
		public void Kill(bool runAnimation=true)
		{
			this.animationPosition = this.Position * Constants.MeterInPixels;
            this.DisableWeapon();
			if (this.world.BodyList.Contains(this.Body) && this.Active)
				this.world.RemoveBody(this.Body);

			if (runAnimation)
				this.CreateDeathAnimation();

			this.Health = 0;
			this.Active = false;
		}

        /// <summary>
        /// Sets up all the actions related to the character exiting a level.
        /// TODO: Rename.
        /// </summary>
        public void Exit()
        {
            // Set the flag to indicate that the exit animation should be drawn.
            this.IsExiting = true;
            // Set the animation position as the current position of the character.
            this.animationPosition = this.getExitPosition();
            // Initialize the animation
            this.CreateExitAnimation();
            // Set the weapon (if there is one) as inactive so that it cannot be controlled.
            this.DisableWeapon();
            // Remove the body so that there are no unwanted collisions.
            this.world.RemoveBody(this.Body);
        }

        protected virtual Vector2 getExitPosition() 
        {
            return this.Position * Constants.MeterInPixels;
        }

        public void DisableWeapon()
        {
            if (this.Weapon != null)
            {
                this.Weapon.Disable();
            }
        }

		/// <summary>
		/// Jumps this character.
		/// </summary>
		public virtual void Jump(bool isForced = false)
		{
			if (this.ReadyToJump() || isForced) 
			{
				this.Body.ApplyLinearImpulse(new Vector2(0, -1 * this.JumpStrength));
				this.JumpsLeft -= 1;
			}
		}

		/// <summary>
		/// Can character still jump.
		/// </summary>
		/// <returns>True if can, False if can't</returns>
		public virtual bool ReadyToJump()
		{
			if (this.JumpsLeft > 0)
				return true;
			return false;
		}

		/// <summary>
		/// Creates the death animation.
		/// </summary>
		protected void CreateDeathAnimation()
		{
			this.DeathAnimation.Initialize(this.CharacterDeathSprite, this.animationPosition,
				Constants.AnimationFrameWidth, Constants.AnimationFrameHeight,
				Constants.AnimationFrameCount, Constants.AnimationFrameTime,
				Color.White, this.SizeFactor, false);
		}

        protected void CreateExitAnimation()
        {
            this.ExitAnimation.Initialize(this.CharacterExitSprite, this.animationPosition,
                Constants.AnimationFrameWidth, Constants.ExitAnimationFrameHeight,
                Constants.AnimationFrameCount, Constants.AnimationFrameTime,
                Color.White, this.SizeFactor, false);
        }

		/// <summary>
		/// Handles the collision while jumping and resets jump counter.
		/// </summary>
		/// <param name="a">Initilizer</param>
		/// <param name="b">Target</param>
		/// <param name="c">Contact type</param>
		/// <returns></returns>
		protected virtual bool HandleCollision(Fixture a, Fixture b, FarseerPhysics.Dynamics.Contacts.Contact c)
		{
			this.JumpsLeft = this.MaxJumps;
			return true;
		}
	}
}
